package com.gitee.ixtf.rsocket.broker

import com.gitee.ixtf.core.J
import com.gitee.ixtf.core.kotlinx.CLOUD_EVENT_FORMAT
import com.gitee.ixtf.rsocket.BROKER_ROUTE
import com.gitee.ixtf.rsocket.auth.CustomCredentials
import com.gitee.ixtf.rsocket.kotlinx.payload
import com.gitee.ixtf.vertx.kotlinx.jsonObject
import com.sun.security.auth.UserPrincipal
import io.cloudevents.CloudEvent
import io.cloudevents.core.builder.CloudEventBuilder
import io.netty.buffer.ByteBufUtil
import io.rsocket.ConnectionSetupPayload
import io.rsocket.Payload
import io.vertx.ext.auth.authentication.AuthenticationProvider
import io.vertx.ext.auth.authentication.Credentials
import io.vertx.ext.auth.authentication.TokenCredentials
import io.vertx.ext.auth.authentication.UsernamePasswordCredentials
import io.vertx.kotlin.coroutines.coAwait
import java.net.URI
import java.security.Principal

object SetupRequest {
  private const val CLOUD_EVENT_SOURCE = "ce:broker:SetupRequest"

  fun service(service: String, credentials: Credentials, id: String = J.objectId()): Payload =
      CloudEventBuilder.v1()
          .withId(id)
          .withSource(URI(CLOUD_EVENT_SOURCE))
          .withType(credentials::class.java.simpleName)
          .withData(credentials.toJson().toBuffer().bytes)
          .withSubject(service)
          .payload(BROKER_ROUTE)

  fun client(credentials: Credentials, id: String = J.objectId()): Payload =
      CloudEventBuilder.v1()
          .withId(id)
          .withSource(URI(CLOUD_EVENT_SOURCE))
          .withType(credentials::class.java.simpleName)
          .withData(credentials.toJson().toBuffer().bytes)
          .payload(BROKER_ROUTE)

  fun decode(setup: ConnectionSetupPayload): CloudEvent =
      CLOUD_EVENT_FORMAT.deserialize(ByteBufUtil.getBytes(setup.sliceData()))

  fun decodeService(event: CloudEvent): String? = event.subject?.takeIf { it.isNotBlank() }

  suspend fun principal(event: CloudEvent, authProvider: AuthenticationProvider): Principal {
    val credentials = decodeCredentials(event)
    val user = authProvider.authenticate(credentials).coAwait()
    return UserPrincipal(user.subject())
  }

  fun decodeCredentials(event: CloudEvent): Credentials =
      when (event.type) {
        UsernamePasswordCredentials::class.java.simpleName ->
            UsernamePasswordCredentials(event.jsonObject())
        TokenCredentials::class.java.simpleName -> TokenCredentials(event.jsonObject())
        CustomCredentials::class.java.simpleName -> CustomCredentials(event.jsonObject())
        else -> throw IllegalAccessError("Credentials: ${event.type}")
      }
}
